Python列表和集合的查找原理

您所在的位置:网站首页 python 字典查找效率 Python列表和集合的查找原理

Python列表和集合的查找原理

#Python列表和集合的查找原理| 来源: 网络整理| 查看: 265

集合与列表查找对比

关于大量数据查找,效率差距到底有多大?

先看一组实例:

import timeimport randomnums = [random.randint(0, 2000000) for i in range(1000)]list_test = list(range(1000000))set_test = set(list_test)count_list, count_set = 0, 0t1 = time.time() #测试在列表中进行查找for num in nums:if num in list_test: count_list += 1t2 = time.time()for num in nums: #测试在集合中进行查找if num in set_test: count_set += 1t3 = time.time() #测试在集合中进行查找print('找到个数,列表:{},集合:{}'.format(count_list, count_set))print('使用时间,列表:{:.4f}s'.format(t2 - t1))print('使用时间,集合:{:.4f}s'.format(t3 - t2))

输出结果为:

找到个数,列表:528,集合:528使用时间,列表:7.9329s使用时间,集合:0.0010s

对于大数据集量来说,我们清晰地看到,集合的查找效率远远的高于列表,那么本文接下来会从Python底层数据结构的角度分析为何出现如此情况。

list列表的原理

Python中的list作为一个常用数据结构,在很多程序中被用来当做数组使用,可能很多人都觉得list无非就是一个动态数组,就像C++中的vector或者Go中的slice一样。但事实真的是这样的吗?

我们来思考一个简单的问题,Python中的list允许我们存储不同类型的数据,既然类型不同,那内存占用空间就就不同,不同大小的数据对象又是如何存入数组中呢?

比如下面的代码中,我们分别在数组中存储了一个字符串,一个整形,以及一个字典对象,假如是数组实现,则需要将数据存储在相邻的内存空间中,而索引访问就变成一个相当困难的事情了,毕竟我们无法猜测每个元素的大小,从而无法定位想要的元素位置。

>>> test = ["hello world", 456, {}]>>> test['hello world', 456, {}]

是通过链表结构实现的吗?毕竟链表支持动态的调整,借助于指针可以引用不同类型的数据。但是这样的话使用下标索引数据的时候,需要依赖于遍历的方式查找,O(n)的时间复杂度访问效率实在是太低。

同时使用链表的开销也较大,每个数据项除了维护本地数据指针外,还要维护一个next指针,因此还要额外分配8字节数据,同时链表分散性使其无法像数组一样利用CPU的缓存来高效的执行数据读写。

实现的细节可以从其Python的源码中找到, 定义如下:

typedef struct { PyObject_VAR_HEAD PyObject **ob_item; Py_ssize_t allocated;} PyListObject;

内部list的实现的是一个C结构体,该结构体中的obitem是一个指针数组,存储了所有对象的指针数据,allocated是已分配内存的数量, PyObjectVAR_HEAD是一个宏扩展包含了更多扩展属性用于管理数组,比如引用计数以及数组大小等内容。

所以我们可以看出,用动态数组作为第一层数据结构,动态数组里存储的是指针,指向对应的数据。

既然是一个动态数组,则必然会面临一个问题,如何进行容量的管理,大部分的程序语言对于此类结构使用动态调整策略,也就是当存储容量达到一定阈值的时候,扩展容量,当存储容量低于一定的阈值的时候,缩减容量。

道理很简单,但实施起来可没那么容易,什么时候扩容,扩多少,什么时候执行回收,每次又要回收多少空闲容量,这些都是在实现过程中需要明确的问题。

假如我们使用一种最简单的策略:超出容量加倍,低于一半容量减倍。这种策略会有什么问题呢?设想一下当我们在容量已满的时候进行一次插入,随即删除该元素,交替执行多次,那数组数据岂不是会不断地被整体复制和回收,已经无性能可言了。

对于Python list的动态调整规则程序中定义如下, 当追加数据容量已满的时候,通过下面的方式计算再次分配的空间大小,创建新的数组,并将所有数据复制到新的数组中。这是一种相对数据增速较慢的策略,回收的时候则当容量空闲一半的时候执行策略,获取新的缩减后容量大小。

具体规则如下:

new_allocated = (newsize >> 3) + (newsize


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3